import {
  ForbiddenException,
  forwardRef,
  Inject,
  Injectable,
} from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';
import { User } from './entities/user.entity';
import * as bcrypt from 'bcrypt';
import { UserRolesPermsVo } from './vo/user-roles-perms.vo';
import { RoleService } from '../role/role.service';
import { PageQuery } from '../../../util/entity/page.entity';
import { buildQuery } from '../../../util/common-fn';
import { ResetPasswordDto } from './dto/reset-password.dto';
import { RoleEnum } from '../../../util/enum/role.enum';
import { SetRolesDto } from './dto/set-roles.dto';
import { QueryUserDto } from './dto/query-user-dto';
import { MoneyService } from '../../game/money/money.service';
import { Money } from '../../game/money/entities/money.entity';
import { FileService } from '../../common/file/file.service';

@Injectable()
export class UserService {
  constructor(
    @Inject(forwardRef(() => MoneyService))
    private moneyService: MoneyService,
    @InjectRepository(User)
    private userRepository: Repository<User>,
    private readonly roleService: RoleService,
    private fileService: FileService,
  ) {}

  /**
   * 新增用户,包括角色
   * @param createUserDto
   */
  async create(createUserDto: CreateUserDto) {
    // 用户名不能重复
    const tempUser: User = await this.getOneByUsername(createUserDto.username);
    if (tempUser) {
      // 如果用户存在,返回错误
      throw new Error('用户已存在');
    }
    // 如果用户设置了角色的话,将角色加入进去
    createUserDto.password = await bcrypt.hash(createUserDto.password, 10);
    await this.userRepository.save(createUserDto);
    // 设置图片已经使用过
    await this.fileService.setUsed(createUserDto.avatar);
    return true;
  }

  /**
   * 用户列表:带分页条件查询,也包括通过角色id查询
   * @param pageQuery
   */
  async findAll(pageQuery: PageQuery<QueryUserDto>) {
    const extraWhere: Record<string, any> = {};
    const { roleId } = pageQuery.query;
    if (roleId) {
      extraWhere.roles = { id: roleId };
    }
    Reflect.deleteProperty(pageQuery.query, 'roleId');
    return await this.userRepository.findAndCount(
      buildQuery(pageQuery, extraWhere),
    );
  }

  /**
   * 更新用户,包含角色
   * tip:使用事务,重要的是需要使用提供的tr,否则会出现数据不一致
   * @param id
   * @param updateUserDto
   */
  async update(id: number, updateUserDto: UpdateUserDto) {
    const result = await this.userRepository.update({ id }, updateUserDto);
    // 设置图片已经使用过
    await this.fileService.setUsed(updateUserDto.avatar);
    return result.affected;
  }

  /**
   * 删除用户,软删除
   * @param id
   */
  async remove(id: number) {
    await this.userRepository.findOneByOrFail({ id });
    return this.userRepository.softDelete({ id });
  }

  async getOneByUsername(username: string): Promise<User> {
    return await this.userRepository.findOneBy({ username });
  }

  async getUserWithMoney(id: number) {
    return await this.userRepository.findOne({
      where: { id },
      relations: {
        moneys: true,
      },
    });
  }

  /**
   *   找到用户的货币
   */
  async getWithMoney(id: number) {
    const user = await this.getUserWithMoney(id);
    const moneys = user.moneys;
    if (moneys.length < 3) {
      const coinMoney = new Money(1, 100000000);
      const couponMoney = new Money(2, 10000);
      const cashMoney = new Money(3, 100);
      await this.moneyService.create(coinMoney);
      await this.moneyService.create(couponMoney);
      await this.moneyService.create(cashMoney);
      user.moneys = [coinMoney, couponMoney, cashMoney];
      await this.userRepository.save(user);
      return await this.getUserWithMoney(id);
    } else {
      return user;
    }
  }

  /**
   * 通过用户id获取用户信息
   * @param id
   */
  getOneByIdOrFail(id: number) {
    return this.userRepository.findOneByOrFail({ id });
  }

  /**
   * 用户id是否拥有roles中的某个角色
   */
  async hasOneOfRoles(id: number, roles: string[]) {
    return !!(await this.userRepository.findOne({
      where: {
        id,
        roles: {
          code: In(roles),
        },
      },
    }));
  }

  /**
   * 某些用户是否含有某些角色
   * @param ids
   * @param roles
   * @private
   */
  private async someHasOneOfRoles(ids: number[], roles: string[]) {
    const users = await this.userRepository.find({
      where: {
        id: In(ids),
        roles: {
          code: In(roles),
        },
      },
    });
    return users.length > 0;
  }

  /**
   * 用户登录的时候,获取roles和perms
   * @param id
   */
  async findOneWithRolesAndMenus(id: number) {
    return await this.userRepository.findOneOrFail({
      where: {
        id,
        status: 1,
        roles: {
          status: 1,
          menus: {
            status: 1,
          },
        },
      },
      relations: {
        roles: {
          menus: true,
        },
      },
    });
  }

  // 得到 id:number,roles:string[],perms:string[]
  async findRolesAndPerms(id: number) {
    const user = await this.findOneWithRolesAndMenus(id);
    const perms = [
      ...new Set(
        user.roles.flatMap((role) =>
          role.menus.flatMap((role) => role.perm).filter((perm) => perm),
        ),
      ),
    ];
    const roles = user.roles.map((role) => role.code);
    const targetUser = new UserRolesPermsVo();
    targetUser.id = user.id;
    targetUser.username = user.username;
    targetUser.nickname = user.nickname;
    targetUser.avatar = user.avatar;
    targetUser.roles = roles;
    targetUser.perms = perms;
    return targetUser;
  }

  /**
   * 重置用户密码
   * @param resetPasswordDto
   */
  async resetPassword(resetPasswordDto: ResetPasswordDto) {
    const { id, password } = resetPasswordDto;
    // 如果是root操作的,或者操作的对象不是root或admin
    const hashPassword = await bcrypt.hash(password, 10);
    const result = await this.userRepository.update(
      { id },
      { password: hashPassword },
    );
    return result.affected;
  }

  /**
   * 用户设置角色
   * @param setRolesDto
   */
  async setRoles(setRolesDto: SetRolesDto) {
    const { id, roleIds } = setRolesDto;
    const user = await this.getOneByIdOrFail(id);
    user.roles = await this.roleService.findByIds(roleIds);
    await this.userRepository.save(user);
    return true;
  }

  /**
   * 不要碰root用户,即使是root用户也不能操作
   * 但root用户能修改自己
   * 返回这两个是否为root
   * @param beOperatedId 被操作的id
   * @param operatorId
   */
  async dontTouchRoot(beOperatedId: number, operatorId: number) {
    const [isBeOperatedRoot] = await this.areTheyRoot(beOperatedId, operatorId);
    // 当被操作者是root,且这两个root的id不一样的时候
    if (isBeOperatedRoot && beOperatedId !== operatorId) {
      throw new Error(`不能对${RoleEnum.Root}进行任何操作`);
    }
  }

  /**
   * 被操作者和操作者是root吗
   * @param beOperatedId
   * @param operatorId
   */
  async areTheyRoot(beOperatedId: number, operatorId: number) {
    return await Promise.all([
      this.hasOneOfRoles(beOperatedId, [RoleEnum.Root]),
      this.hasOneOfRoles(operatorId, [RoleEnum.Root]),
    ]);
  }

  /**
   * 检查操作者的状态是否正常
   * @param operatorId
   */
  async isStatusOk(operatorId: number) {
    const user = await this.userRepository.findOneBy({
      id: operatorId,
      status: 1,
    });
    if (!user) {
      throw new ForbiddenException('您一已被禁用');
    }
  }

  // 请求之前,检查一些行为是否合理
  async checkActions(beOperatedId: number, operatorId: number) {
    // 1.检查这个操作者是否状态正常
    // await this.isStatusOk(operatorId);
    // 2.检查是否更改root用户的信息
    await this.dontTouchRoot(beOperatedId, operatorId);
  }

  // 批量删除
  async batchDelete(ids: number[]) {
    const has = await this.someHasOneOfRoles(ids, [RoleEnum.Root]);
    if (has) {
      throw new Error('其中包含' + RoleEnum.Root + '用户,不能删除');
    }
    const result = await this.userRepository.softDelete({ id: In(ids) });
    return result.affected;
  }
  // 批量赋予角色
  async batchSetRoles(ids: number[], roleIds: number[]) {
    const idHas = await this.someHasOneOfRoles(ids, [RoleEnum.Root]);
    const roleIdHas = await this.roleService.hasRootByIds(roleIds);
    if (idHas) {
      throw new Error('其中包含' + RoleEnum.Root + '用户,不能分配');
    }
    if (roleIdHas) {
      throw new Error('其中包含' + RoleEnum.Root + '角色,不能分配');
    }
    // 先移除这些角色,然后再添加进来,直接添加的话,有重复的会报错
    await this.userRepository
      .createQueryBuilder()
      .relation(User, 'roles')
      .of(ids)
      .remove(roleIds)
      .then(() => {
        this.userRepository
          .createQueryBuilder()
          .relation(User, 'roles')
          .of(ids)
          .add(roleIds);
      });
  }
}
